import socket
import threading
import json
import time
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext, filedialog
import datetime
import configparser
import os
import netifaces
import pandas as pd


class NetworkLabelServer:
    def __init__(self, root):
        self.root = root
        self.root.title("网络线路标号系统 - 服务器端")
        self.root.geometry("900x600")
        self.root.minsize(800, 500)

        # 服务器配置 - 初始值会被load_config覆盖
        self.host = "0.0.0.0"
        self.port = 12345
        self.server_socket = None
        self.running = False
        # 使用MAC地址作为唯一标识，避免重复
        self.clients = {}  # key: mac_address, value: {socket, address, info, last_seen, status, note, offline_alerted}
        self.logs = []

        # 加载配置文件（优先读取配置）
        self.config_file = "config.ini"
        self.load_config()

        # 创建UI
        self.create_widgets()

        # 启动服务器线程
        self.start_server()

    def create_widgets(self):
        # 顶部配置区域
        config_frame = ttk.LabelFrame(self.root, text="服务器配置")
        config_frame.pack(fill=tk.X, padx=10, pady=5)

        ttk.Label(config_frame, text="服务器IP:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)

        # 获取可用IP地址
        self.ip_addresses = self.get_available_ip_addresses()
        self.host_var = tk.StringVar(value=self.host)

        # 创建IP选择组合框
        self.ip_combobox = ttk.Combobox(
            config_frame,
            textvariable=self.host_var,
            values=self.ip_addresses,
            width=15
        )
        self.ip_combobox.grid(row=0, column=1, padx=5, pady=5)

        ttk.Label(config_frame, text="端口:").grid(row=0, column=2, padx=5, pady=5, sticky=tk.W)
        self.port_var = tk.StringVar(value=str(self.port))
        ttk.Entry(config_frame, textvariable=self.port_var, width=8).grid(row=0, column=3, padx=5, pady=5)

        # 自动选择端口按钮
        ttk.Button(config_frame, text="自动选择端口", command=self.auto_select_port).grid(row=0, column=4, padx=5,
                                                                                          pady=5)

        self.status_var = tk.StringVar(value="未运行")
        ttk.Label(config_frame, text="状态:").grid(row=0, column=5, padx=5, pady=5, sticky=tk.W)

        # 状态标签，单独设置前景色
        self.status_label = ttk.Label(config_frame, textvariable=self.status_var, foreground="red")
        self.status_label.grid(row=0, column=6, padx=5, pady=5)

        ttk.Button(config_frame, text="重启服务器", command=self.restart_server).grid(row=0, column=7, padx=5, pady=5)

        # 导出按钮移到此处
        ttk.Button(config_frame, text="导出", command=self.save_to_excel).grid(row=0, column=8, padx=5, pady=5)

        # 中部设备列表
        devices_frame = ttk.LabelFrame(self.root, text="连接设备列表")
        devices_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

        # 创建表格
        columns = ("ip", "mac", "room", "computer", "status", "note")
        self.device_tree = ttk.Treeview(devices_frame, columns=columns, show="headings")

        # 设置列标题
        self.device_tree.heading("ip", text="IP地址")
        self.device_tree.heading("mac", text="MAC地址")
        self.device_tree.heading("room", text="房间号")
        self.device_tree.heading("computer", text="电脑名")
        self.device_tree.heading("status", text="状态")
        self.device_tree.heading("note", text="备注")

        # 设置列宽
        self.device_tree.column("ip", width=120)
        self.device_tree.column("mac", width=160)
        self.device_tree.column("room", width=80)
        self.device_tree.column("computer", width=120)
        self.device_tree.column("status", width=80)
        self.device_tree.column("note", width=200)

        # 添加滚动条
        scrollbar = ttk.Scrollbar(devices_frame, orient=tk.VERTICAL, command=self.device_tree.yview)
        self.device_tree.configure(yscroll=scrollbar.set)

        # 绑定双击事件用于编辑备注
        self.device_tree.bind("<Double-1>", self.on_double_click)

        # 布局表格和滚动条
        self.device_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        # 表格操作按钮
        btn_frame = ttk.Frame(devices_frame)
        btn_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=5)

        ttk.Button(btn_frame, text="刷新列表", command=self.refresh_list).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="清除离线设备", command=self.clear_offline).pack(side=tk.LEFT, padx=5)

        # 底部日志区域
        log_frame = ttk.LabelFrame(self.root, text="系统日志")
        log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

        self.log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, height=8)
        self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        self.log_text.config(state=tk.DISABLED)

    def on_double_click(self, event):
        """处理双击事件，编辑备注"""
        # 获取双击位置的列和行
        region = self.device_tree.identify_region(event.x, event.y)
        column = self.device_tree.identify_column(event.x)
        item = self.device_tree.identify_row(event.y)

        # 只处理备注列的双击事件
        if region == "cell" and column == "#6" and item:  # #6对应备注列
            self.edit_note(item)

    def edit_note(self, item):
        """编辑备注信息"""
        # 获取选中设备的信息
        item_values = self.device_tree.item(item, "values")
        mac_address = item_values[1]  # 使用MAC地址作为唯一标识

        # 查找对应的客户端
        if mac_address in self.clients:
            client_data = self.clients[mac_address]
        else:
            messagebox.showinfo("提示", "未找到该设备的信息")
            return

        # 获取当前备注
        current_note = client_data['note']

        # 创建备注输入对话框
        note_dialog = tk.Toplevel(self.root)
        note_dialog.title("编辑备注")
        note_dialog.geometry("300x150")
        note_dialog.transient(self.root)
        note_dialog.grab_set()

        ttk.Label(note_dialog, text="请输入备注:").pack(padx=10, pady=10, anchor=tk.W)

        note_text = tk.Text(note_dialog, height=4, wrap=tk.WORD)
        note_text.pack(fill=tk.X, padx=10, pady=5)
        note_text.insert(tk.END, current_note)

        def save_note():
            note = note_text.get("1.0", tk.END).strip()
            self.clients[mac_address]['note'] = note
            self.refresh_list()
            note_dialog.destroy()

        btn_frame = ttk.Frame(note_dialog)
        btn_frame.pack(fill=tk.X, padx=10, pady=10)

        ttk.Button(btn_frame, text="保存", command=save_note).pack(side=tk.RIGHT, padx=5)
        ttk.Button(btn_frame, text="取消", command=note_dialog.destroy).pack(side=tk.RIGHT, padx=5)

    def get_available_ip_addresses(self):
        """获取本机所有可用的IP地址"""
        ip_addresses = ["0.0.0.0"]  # 允许所有接口

        try:
            # 获取所有网络接口
            interfaces = netifaces.interfaces()
            for iface in interfaces:
                # 跳过本地回环接口
                if iface.startswith('lo'):
                    continue

                # 获取IPv4地址
                addrs = netifaces.ifaddresses(iface)
                if netifaces.AF_INET in addrs:
                    ip_info = addrs[netifaces.AF_INET][0]
                    ip_address = ip_info.get('addr', '')
                    if ip_address and ip_address != '127.0.0.1':
                        ip_addresses.append(ip_address)
        except Exception as e:
            self.log(f"获取IP地址时出错: {str(e)}")

        return list(set(ip_addresses))  # 去重

    def auto_select_port(self):
        """自动选择一个未被占用的端口"""
        start_port = 12345
        max_port = 12400

        for port in range(start_port, max_port + 1):
            try:
                # 尝试绑定端口，如果成功则说明端口未被占用
                s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                s.bind(('localhost', port))
                s.close()
                self.port_var.set(str(port))
                self.log(f"已选择可用端口: {port}")
                return
            except:
                continue

        messagebox.showwarning("警告", "在范围内未找到可用端口，请手动指定")

    def load_config(self):
        """从配置文件加载IP和端口，无配置则自动获取"""
        if os.path.exists(self.config_file):
            config = configparser.ConfigParser()
            config.read(self.config_file)
            if 'Server' in config:
                self.host = config['Server'].get('IP', self.host)
                self.port = int(config['Server'].get('Port', str(self.port)))
        else:
            # 无配置文件时，自动获取第一个可用IP
            ips = self.get_available_ip_addresses()
            if len(ips) > 1:
                self.host = ips[1]  # 取第一个非0.0.0.0的IP
            # 端口保持默认12345

    def save_config(self):
        """保存IP和端口到配置文件"""
        config = configparser.ConfigParser()
        config['Server'] = {
            'IP': self.host_var.get(),
            'Port': self.port_var.get()
        }

        with open(self.config_file, 'w') as f:
            config.write(f)

        self.log(f"配置已保存到 {self.config_file}")

    def start_server(self):
        """启动服务器"""
        try:
            self.host = self.host_var.get()
            self.port = int(self.port_var.get())

            self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.server_socket.bind((self.host, self.port))
            self.server_socket.listen(50)  # 允许最多50个连接

            self.running = True
            self.status_var.set(f"运行中 ({self.host}:{self.port})")
            self.status_label.config(foreground="green")

            # 保存配置
            self.save_config()

            # 启动服务器监听线程
            self.server_thread = threading.Thread(target=self.accept_connections, daemon=True)
            self.server_thread.start()

            # 启动客户端监测线程
            self.monitor_thread = threading.Thread(target=self.monitor_clients, daemon=True)
            self.monitor_thread.start()

            self.log(f"服务器已启动，监听 {self.host}:{self.port}")
        except Exception as e:
            messagebox.showerror("启动失败", f"无法启动服务器: {str(e)}")
            self.log(f"服务器启动失败: {str(e)}")
            self.status_var.set("启动失败")
            self.status_label.config(foreground="red")

    def restart_server(self):
        """重启服务器"""
        if self.running:
            self.running = False
            if self.server_socket:
                self.server_socket.close()
            self.status_var.set("重启中...")
            self.status_label.config(foreground="orange")
            self.log("正在重启服务器...")

        # 延迟启动以确保关闭完成
        self.root.after(1000, self.start_server)

    def accept_connections(self):
        """接受客户端连接"""
        while self.running:
            try:
                client_socket, client_address = self.server_socket.accept()
                client_ip = client_address[0]

                # 启动客户端处理线程
                client_thread = threading.Thread(
                    target=self.handle_client,
                    args=(client_socket, client_address),
                    daemon=True
                )
                client_thread.start()

                self.log(f"新连接来自 {client_ip}")
            except Exception as e:
                if self.running:  # 只有在服务器运行时才记录错误
                    self.log(f"接受连接时出错: {str(e)}")
                break

    def handle_client(self, client_socket, client_address):
        """处理客户端通信，使用MAC地址作为唯一标识"""
        client_ip = client_address[0]
        mac_address = None
        try:
            # 接收客户端信息
            data = client_socket.recv(1024).decode('utf-8')
            if data:
                device_info = json.loads(data)
                # 补充IP地址（从连接中获取）
                device_info['ip_address'] = client_ip
                mac_address = device_info.get('mac_address')

                if mac_address:
                    # 检查设备是否已存在，存在则更新信息，不存在则添加
                    if mac_address in self.clients:
                        # 更新现有设备信息
                        existing_data = self.clients[mac_address]
                        existing_data.update({
                            'socket': client_socket,
                            'address': client_address,
                            'info': device_info,
                            'last_seen': datetime.datetime.now(),
                            'status': '在线',
                            'offline_alerted': False  # 重新上线后重置提醒标记
                        })
                        self.log(f"设备 {mac_address} 重新上线，更新信息")
                    else:
                        # 添加新设备
                        self.clients[mac_address] = {
                            'socket': client_socket,
                            'address': client_address,
                            'info': device_info,
                            'last_seen': datetime.datetime.now(),
                            'status': '在线',
                            'note': '',
                            'offline_alerted': False  # 新增设备初始化提醒标记
                        }
                        self.log(f"新设备 {mac_address} 上线")

                    self.refresh_list()

                    # 保持连接，定期发送心跳请求
                    while self.running:
                        time.sleep(5)  # 每5秒检查一次
                        if mac_address in self.clients:  # 确保设备信息仍存在
                            self.clients[mac_address]['last_seen'] = datetime.datetime.now()

                            # 发送心跳包检查
                            try:
                                client_socket.sendall(b'ping')
                            except:
                                break
                else:
                    self.log(f"来自 {client_ip} 的设备未提供MAC地址，无法识别")
        except Exception as e:
            self.log(f"与 {client_ip} 通信时出错: {str(e)}")
        finally:
            # 客户端断开连接
            if mac_address and mac_address in self.clients:
                # 只有当状态从在线变为离线时才提醒
                if self.clients[mac_address]['status'] == '在线':
                    self.clients[mac_address]['status'] = '离线'
                    self.log(f"设备 {mac_address} 已断开连接")
                    self.refresh_list()
                    # 显示断线提示（只提醒一次）
                    self.root.after(0, self.show_disconnect_alert, mac_address)

            try:
                client_socket.close()
            except:
                pass

    def show_disconnect_alert(self, mac_address):
        """显示设备断线提示，确保只提醒一次"""
        if mac_address in self.clients and not self.clients[mac_address].get('offline_alerted', False):
            info = self.clients[mac_address]['info']
            message = f"设备已断线:\nIP: {info.get('ip_address')}\nMAC: {info.get('mac_address')}\n房间: {info.get('room_number')}\n电脑名: {info.get('computer_name')}"
            messagebox.showwarning("设备断线", message)

            # 标记为已提醒
            self.clients[mac_address]['offline_alerted'] = True

    def monitor_clients(self):
        """监测客户端状态，只在状态变化时记录日志"""
        while self.running:
            time.sleep(10)  # 每10秒检查一次
            now = datetime.datetime.now()

            # 检查是否有客户端超时
            for mac_address, client_data in list(self.clients.items()):
                # 只有当前状态是在线，且超时的情况才处理
                if client_data['status'] == '在线' and (now - client_data['last_seen']).total_seconds() > 15:
                    client_data['status'] = '离线'
                    self.log(f"设备 {mac_address} 已超时离线")
                    # 显示断线提示（只提醒一次）
                    self.root.after(0, self.show_disconnect_alert, mac_address)

            self.refresh_list()

    def refresh_list(self):
        """刷新设备列表，断线设备置顶显示"""
        # 清空现有项
        for item in self.device_tree.get_children():
            self.device_tree.delete(item)

        # 分离在线和离线设备，离线设备置顶
        offline_clients = []
        online_clients = []

        for client_data in self.clients.values():
            if client_data['status'] == '离线':
                offline_clients.append(client_data)
            else:
                online_clients.append(client_data)

        # 先添加离线设备（置顶），再添加在线设备
        all_clients = offline_clients + online_clients

        # 添加所有客户端
        for client_data in all_clients:
            info = client_data['info']
            status = client_data['status']
            note = client_data['note']

            # 设置状态颜色
            tag = "online" if status == "在线" else "offline"

            self.device_tree.insert(
                "", tk.END,
                values=(
                    info.get('ip_address', ''),
                    info.get('mac_address', ''),
                    info.get('room_number', ''),
                    info.get('computer_name', ''),
                    status,
                    note
                ),
                tags=(tag,)
            )

        # 配置标签颜色
        self.device_tree.tag_configure("online", foreground="green")
        self.device_tree.tag_configure("offline", foreground="red")

    def clear_offline(self):
        """清除所有离线设备 - 修改为只清除可以删除的离线设备（实际中我们不删除任何断线设备）"""
        # 统计要清除的设备数量（实际不清除，只做日志）
        offline_count = sum(1 for data in self.clients.values() if data['status'] == '离线')

        if offline_count > 0:
            messagebox.showinfo("提示", f"断线设备不允许删除，当前有{offline_count}个断线设备")
        else:
            messagebox.showinfo("提示", "没有可清除的离线设备")

    def save_to_excel(self):
        """将设备列表保存到Excel文件，确保使用openpyxl引擎"""
        if not self.clients:
            messagebox.showinfo("提示", "没有设备信息可保存")
            return

        # 获取保存路径
        file_path = filedialog.asksaveasfilename(
            defaultextension=".xlsx",
            filetypes=[("Excel files", "*.xlsx"), ("All files", "*.*")],
            title="保存设备列表"
        )

        if not file_path:
            return

        try:
            # 准备数据
            data = []
            for client_data in self.clients.values():
                info = client_data['info']
                data.append({
                    'IP地址': info.get('ip_address', ''),
                    'MAC地址': info.get('mac_address', ''),
                    '房间号': info.get('room_number', ''),
                    '电脑名': info.get('computer_name', ''),
                    '状态': client_data['status'],
                    '备注': client_data['note']
                })

            # 创建DataFrame并保存，明确指定engine为openpyxl
            df = pd.DataFrame(data)

            # 尝试多种方式导出，确保兼容性
            try:
                df.to_excel(file_path, index=False, engine='openpyxl')
            except:
                # 如果openpyxl不可用，尝试不指定引擎
                df.to_excel(file_path, index=False)

            self.log(f"设备列表已保存到 {file_path}")
            messagebox.showinfo("成功", f"设备列表已成功保存到:\n{file_path}")
        except Exception as e:
            self.log(f"保存Excel文件时出错: {str(e)}")
            # 提供安装openpyxl的提示
            if "openpyxl" in str(e).lower():
                messagebox.showerror(
                    "错误",
                    f"保存文件时出错: 需要安装openpyxl库\n请运行: pip install openpyxl\n{str(e)}"
                )
            else:
                messagebox.showerror("错误", f"保存文件时出错:\n{str(e)}")

    def log(self, message):
        """添加日志信息"""
        timestamp = datetime.datetime.now().strftime("%H:%M:%S")
        log_entry = f"[{timestamp}] {message}\n"

        self.log_text.config(state=tk.NORMAL)
        self.log_text.insert(tk.END, log_entry)
        self.log_text.see(tk.END)  # 滚动到最后
        self.log_text.config(state=tk.DISABLED)

        # 保存到日志列表
        self.logs.append(log_entry)
        # 限制日志数量
        if len(self.logs) > 1000:
            self.logs = self.logs[-500:]


if __name__ == "__main__":
    root = tk.Tk()
    app = NetworkLabelServer(root)
    root.mainloop()
